#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <iostream>
#include <fstream>
#include <string.h>
#include <queue>

void error(const char *msg)
{
    perror(msg);
    exit(1);
}

class Reader {		

	public:

		Reader( int h_file, bool initially_read=false )
		{
			this->h_file = h_file;
			this->buff = NULL;
			if( initially_read )
				this->readData();
			else
				this->clearBuff();
		}

		~Reader()
		{
			clearBuff();
		}

		int getBuffSize()
		{
			return this->buff_size;
		}

		char* getBuff()
		{
			return this->buff;
		}

		int getInt()
		{
                        int result = 0;
                        if( this->getBuffSize() == sizeof(int) )
                        	memcpy(&result,this->getBuff(),sizeof(result));
			else
				error( "Data in buff is not Int" );
                        return result;			
		}

		char getChar()
		{
                        char result = 0;
			if( this->getBuffSize() == sizeof(char) )
                        	memcpy(&result,this->getBuff(),sizeof(result));
			else
				error( "data in buff is not Char" );
                        return result;			
		}

		int readToBuff( char* buff, int buff_size )
		{
			if( buff_size <= 0 )
				return buff_size;
			int result = -1;
			int to_read = buff_size;
			int readed = 0;
			int n = to_read;

			bzero( buff, buff_size );

			while( n > 0 && to_read > 0 ) {
				n = read(this->h_file, buff+readed, to_read);
				readed += n;
				to_read -= n;
			}

			if( n < 0 )
				error( "ERRPR while readBuff" );

			return readed;	
		}
		
		Reader* readData( int size = -1 )
		{	
			this->clearBuff();
			this->buff_size = size;
			if( this->buff_size <= 0 ) {
				if( readToBuff((char*)&(this->buff_size),sizeof(this->buff_size)) != sizeof(this->buff_size) )
					error( "ERROR while reading buff_size" );
			}
			this->buff = new char[ this->buff_size+1 ];
			bzero( this->buff, this->buff_size );
			if( readToBuff(this->buff,this->buff_size) != this->buff_size )
				error( "ERROR while reading buff" );
			this->buff[ this->buff_size ] = '\0';
			return this;
		}

		Reader* readInt()
		{
			return this->readData( sizeof(int) );
		}

		Reader* readChar()
		{
			return this->readData( sizeof(char) );
		}

        private:
                int buff_size;
                char* buff;
                int h_file;

                void clearBuff()
                {
                        if( this->buff != NULL )
                                delete [] this->buff;
                        this->buff_size = -1;
                        this->buff = NULL;
                }

};

void createDir(const char *dir_name) {
	if( mkdir( dir_name, 0777 ) == 0 )
		std::cout << "Creating directory: " << dir_name << std::endl;
	else {
		std::string cmd = std::string("mkdir -p ");
		cmd += dir_name;
		if( system( cmd.c_str() ) == 0 ) 
			std::cout << "Creating directory: " << dir_name << std::endl;
		else
			std::cout << "Error at creating directory" << std::endl;
	}
}

void createOrChangeFile(const char *file_name, const char *new_data, int size) {
	//std::ofstream file( file_name, std::ios::out|std::ios::binary|std::ios::trunc );
	std::ofstream file( file_name, std::ofstream::binary );
	if( file.is_open() ) {
		file.write( new_data, size );
		file.close();
		std::cout << "Writing " << size << " bytes into file: " << file_name << std::endl;
	} else
		std::cout << "Error at writing file: " << file_name << std::endl;
}

void mvFileOrDir(const char *old_src, const char *new_src) {
	rename( old_src, new_src );
	std::cout << "Moving file or dict from: " << old_src << " to: " << new_src << std::endl;
}

void deleteFileOrDir(const char *file_or_dir_name) {
	if( remove( file_or_dir_name ) == 0 )
		std::cout << "Deleting file: " << file_or_dir_name << std::endl;
	else {
		std::string cmd = std::string("rm -rf ");
		cmd += file_or_dir_name;
		if( system(cmd.c_str()) == 0 )
			std::cout << "Deleting file: " << file_or_dir_name << std::endl;
		else
			std::cout << "Error at deleting file: " << file_or_dir_name << std::endl;
	}		
}

void execute( int f_handle, const char* dest_url )
{
	Reader rcmd( f_handle );
	Reader rsrc( f_handle );
	std::string src( dest_url );
	Reader rdata( f_handle );
	Reader rdest( f_handle );
	std::string dest( dest_url );
	char* buff = NULL;
	int buff_size = -1;

	switch( rcmd.readChar()->getChar() ) {
                case 0:
			src += rsrc.readData()->getBuff();
			createDir( src.c_str() );
			break;
                case 1:
			src += rsrc.readData()->getBuff();
			buff = rdata.readData()->getBuff();
			buff_size = rdata.getBuffSize();
			createOrChangeFile( src.c_str(), buff, buff_size );
                        break;
                case 2:
			src += rsrc.readData()->getBuff();
			dest += rdest.readData()->getBuff();
			mvFileOrDir( src.c_str(), dest.c_str() );
                        break;
                case 3:
			src += rsrc.readData()->getBuff();
			deleteFileOrDir( src.c_str() );
                        break;
                default:
                        std::cout << "Undefined command" << std::endl;
        }
	
}

int main(int argc, char *argv[])
{
     int sockfd, newsockfd, portno;
     socklen_t clilen;
     struct sockaddr_in serv_addr, cli_addr;
     const char* dir_src = NULL;

     dir_src = argc > 2 ? argv[2] : "/home/marcin/workspace/";
     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0) 
        error("ERROR opening socket");
     bzero((char *) &serv_addr, sizeof(serv_addr));
     portno = argc > 1 ? atoi(argv[1]) : 8022;
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);
     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0) 
              error("ERROR on binding");
     while( true ) {
     	listen(sockfd,5);
     	clilen = sizeof(cli_addr);
     	newsockfd = accept(sockfd, 
                 (struct sockaddr *) &cli_addr, 
                 &clilen);
     	if (newsockfd < 0) 
        	error("ERROR on accept");

	execute( newsockfd, dir_src );
	
	close(newsockfd);
     }
     close(sockfd);
     return 0; 
}

